﻿using System;
using System.Collections.Generic;
using Internal.KuiHuaBaoDian.Services.Net;
using KuiHuaBaoDian.Services.Net;
using UnityEngine.Assertions;

namespace KuiHuaBaoDian.Services.Net {

    /// <summary>
    /// 接收器
    /// </summary>
    public interface INetReceiver {

        void AddListener(INetPacketID packetID, NetCallbackID callbackID, Action<INetPacket> callback, bool isOneTime = false);

        void RemoveListener(INetPacketID packetID, NetCallbackID callbackID);
    }

    [Implement(typeof(INetReceiver))]
    internal sealed class KHBD_NetReceiver : INetReceiver, INetReceiverInternal, IDisposable {

        private class CallbackRecord {

            public CallbackRecord(NetCallbackID id, Action<INetPacket> action, bool isOneTime = false) {
                ID = id;
                Action = action;
                IsOneTime = isOneTime;
            }

            public NetCallbackID ID { get; private set; }

            public Action<INetPacket> Action { get; private set; }

            public bool IsOneTime { get; private set; }

            public void Dispose() {
                Action = null;
            }
        }

        private readonly struct PacketIDHash {
            public int Value { get; init; }
            public override int GetHashCode() => Value;
            public override bool Equals(object obj) {
                if (obj is PacketIDHash other) {
                    return other.Value == Value;
                }
                return false;
            }
            public static bool operator ==(PacketIDHash left, PacketIDHash right) => left.Equals(right);
            public static bool operator !=(PacketIDHash left, PacketIDHash right) => !left.Equals(right);
        }
        private Dictionary<PacketIDHash, IList<CallbackRecord>> m_CallbackListDictionary = new();
        private Dictionary<NetCallbackID, CallbackRecord> m_CallbackDictionary = new();

        private PacketIDHash m_DispatchingPacketIDHash;

        private Queue<CallbackRecord> m_AddingCallbackQueue = new();
        private Queue<CallbackRecord> m_RemovingCallbackQueue = new();

        void IDisposable.Dispose() {
            foreach (var kv in m_CallbackDictionary) {
                kv.Value.Dispose();
            }
            m_CallbackDictionary = null;
            m_CallbackListDictionary = null;
            m_AddingCallbackQueue = null;
            m_RemovingCallbackQueue = null;
        }

        public void AddListener(INetPacketID packetID, NetCallbackID callbackID, Action<INetPacket> callback, bool isOneTime = false) {
            Assert.IsNotNull(callback);

            var record = new CallbackRecord(callbackID, callback, isOneTime);
            m_CallbackDictionary.Add(callbackID, record);

            var hash = new PacketIDHash { Value = packetID.GetHashCode() };
            if (!m_CallbackListDictionary.TryGetValue(hash, out var list)) {
                list = new List<CallbackRecord>();
                m_CallbackListDictionary[hash] = list;
            }
            if (m_DispatchingPacketIDHash != hash) {
                list.Add(record);
            } else {
                m_AddingCallbackQueue.Enqueue(record);
            }
        }

        public void RemoveListener(INetPacketID packetID, NetCallbackID callbackID) {
            if (m_CallbackDictionary != null && m_CallbackDictionary.TryGetValue(callbackID, out var record)) {
                m_CallbackDictionary.Remove(callbackID);

                var hash = new PacketIDHash { Value = packetID.GetHashCode() };
                if (m_CallbackListDictionary.TryGetValue(hash, out var list)) {
                    if (m_DispatchingPacketIDHash != hash) {
                        list.Remove(record);
                    } else {
                        m_RemovingCallbackQueue.Enqueue(record);
                    }
                }
            }
        }

        void INetReceiverInternal.Dispatch(INetPacket packet) {
            var packetID = packet.ID;
            var hash = new PacketIDHash { Value = packetID.GetHashCode() };
            if (m_CallbackListDictionary.TryGetValue(hash, out var list)) {
                m_DispatchingPacketIDHash = hash;
                CallbackRecord oneTimeExecutedRecord = null;
                foreach (var record in list) {
                    if (!record.IsOneTime || (record.IsOneTime && oneTimeExecutedRecord == null)) {
                        record.Action.Invoke(packet);
                        if (record.IsOneTime) {
                            oneTimeExecutedRecord = record;
                        }
                    }
                }
                m_DispatchingPacketIDHash = new();
                if (oneTimeExecutedRecord != null) {
                    RemoveListener(packetID, oneTimeExecutedRecord.ID);
                }
                while (m_AddingCallbackQueue.Count > 0 && KHBD.Context.IsApplicationPlaying) {
                    var addingRecord = m_AddingCallbackQueue.Dequeue();
                    AddListener(packetID, addingRecord.ID, addingRecord.Action, addingRecord.IsOneTime);
                }
                while (m_RemovingCallbackQueue.Count > 0 && KHBD.Context.IsApplicationPlaying) {
                    RemoveListener(packetID, m_RemovingCallbackQueue.Dequeue().ID);
                }
            }
        }
    }
}

namespace Internal.KuiHuaBaoDian.Services.Net {

    public interface INetReceiverInternal {

        void Dispatch(INetPacket packet);
    }
}
